using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.IO;
using System.Security.Permissions;
using System.Security;
using System.Reflection;
using Saxon.Api;
using net.sf.saxon.om;
using System.Configuration;
using System.Collections;

namespace NAVA.MTSLibrary
{
    
    public class MessageTester
    {
        #region PROPERTIES
        private static MessageTester msgTester = null;
        private StringBuilder   SchemaResults = null;
        private StringBuilder SchemaErrors = null;
        private string sSchemaCache = "";
        private int schemaErrorCount = 0;
        private TimeSpan elapsedSchemaSpan;
        private TimeSpan elapsedRulesetSpan;
        private string sTranType = "";
        private string sSubTranType = "";
        private string sTranTypeTC = "";
        private string sSubTranTypeTC = "";
        private Processor processor = null;
     
        
        public string SchemaCache
        {
            get { return sSchemaCache; }
        }
        #endregion

        #region CONSTRUCTOR
        private MessageTester() { }
        #endregion

        #region FACTORIES
        public static MessageTester MessageTesterFactory()
        {
            return MessageTester.MessageTesterFactory("");
        }

        public static MessageTester MessageTesterFactory(string sSchemaLocation)
        {
            if (msgTester == null)
            {
                if (sSchemaLocation.Length == 0)
                {
                    if (ConfigurationManager.AppSettings["SchemaLocation"] == null)
                        throw new Exception("SchemaLocation not found in the configuration file");
                    sSchemaLocation = ConfigurationManager.AppSettings["SchemaLocation"];
                }


                msgTester = new MessageTester();
                msgTester.processor = new Processor();
            }
            msgTester.sSchemaCache = sSchemaLocation;
            
            if(msgTester.sSchemaCache.Length == 0)
               throw new Exception("SchemaLocation not set in the configuration file");

            if (!msgTester.sSchemaCache.EndsWith("\\"))
                msgTester.sSchemaCache += "\\";
            

            return msgTester;
        }
        #endregion

        #region METHODS
        private string validateSchema(string inputMessage)
        {
            DateTime startDateTime = DateTime.Now;
            //http://articles.techrepublic.com.com/5100-10878_11-5080856.html#

            schemaErrorCount = 0;
            //First get the name of the schema file from the input message
            string strSchema = FetchSchemaDefinition(inputMessage);
            if (strSchema == "") return "";

            XmlLocalResolver lr = new XmlLocalResolver(SchemaCache);
            Uri s = lr.BaseURI;

            SchemaErrors = new StringBuilder();
            SchemaResults = new StringBuilder();
            XmlReaderSettings readerSettings = new XmlReaderSettings();
            readerSettings.XmlResolver = lr;           
            readerSettings.ValidationType = ValidationType.Schema;            
            readerSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;            
         //   readerSettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;

            readerSettings.Schemas.Add("", s.LocalPath + strSchema);
            readerSettings.ValidationEventHandler += new ValidationEventHandler(this.ValidationEventHandler);
            readerSettings.CloseInput = true;
            XmlDocument xMessage = new XmlDocument();
            xMessage.Load(
                XmlReader.Create(new StringReader(inputMessage),readerSettings));

            DateTime endDateTime = DateTime.Now;
            long elapsedTicks = endDateTime.Ticks - startDateTime.Ticks;
            elapsedSchemaSpan = new TimeSpan(elapsedTicks);
            SchemaResults.Append("<SchemaResults>");
            SchemaResults.Append(SchemaErrors.ToString());
            SchemaResults.Append("</SchemaResults>");

            Hashtable sd = new Hashtable();
            
            sd.Add("schemafile", new XdmAtomicValue(strSchema));
            sd.Add("schemaruntime", new XdmAtomicValue(string.Format("{0}m.{1}s.{2}ms",
                elapsedSchemaSpan.Minutes, elapsedSchemaSpan.Seconds, elapsedSchemaSpan.Milliseconds)));
            XmlDocument xOut = new XmlDocument();
            xOut.LoadXml(SchemaResults.ToString());
            XmlDocument inXsl = loadInternalXmlDocument("schemaResponse.xslt");

            return transform("SchemaResponse", xOut, new System.IO.StringReader(inXsl.OuterXml), sd);
            
        }

        protected string FetchSchemaDefinition(string strXml)
        {
            string strResponse = "";

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(strXml);

            XmlNode xnRoot;
            xnRoot = xmlDoc.FirstChild;
            if (xnRoot.Name == "xml")
            {
                xnRoot = xnRoot.NextSibling;
            }
            while (xnRoot.Name == "#comment")
            {
                xnRoot = xnRoot.NextSibling;
            }

            XmlAttribute xaSchema = xnRoot.Attributes["xsi:schemaLocation"];
            if (xaSchema != null)
            {
                strResponse = xaSchema.Value;
                int iFirstSpace = strResponse.IndexOf(' ');
                strResponse = strResponse.Substring(iFirstSpace + 1);
            }
            else
            {
                xaSchema = xnRoot.Attributes["xsi:noNamespaceSchemaLocation"];
                if (xaSchema != null)
                {
                    strResponse = xaSchema.Value;
                    int iFirstSlash = strResponse.LastIndexOf('\\');
                    if (iFirstSlash > -1)
                    {
                        strResponse = strResponse.Substring(iFirstSlash + 1);
                    }
                    else
                    {
                        iFirstSlash = strResponse.LastIndexOf('/');
                        if (iFirstSlash > -1)
                        {
                            strResponse = strResponse.Substring(iFirstSlash + 1);
                        }
                    }
                }
               
            }
            
            xmlDoc = null;
            
            return (strResponse);

        }

        protected void ValidationEventHandler(Object sender, ValidationEventArgs e)
        { 
            XmlSchemaException ee = e.Exception;            
            SchemaErrors.Append("<ValidationEvent>");
            SchemaErrors.AppendFormat("<Message>{0}</Message>", ee.Message);
            SchemaErrors.AppendFormat("<Source>Line {0} Position {1}</Source>",
                ee.LineNumber, ee.LinePosition);
            SchemaErrors.Append("</ValidationEvent>");
        }

        public string evaluateExpression(string inputMessage, string expression)
        {
            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml(inputMessage);

            XmlDocument inXsl = loadInternalXmlDocument("eval.xslt");
            Hashtable sd = new Hashtable();
            sd.Add("expr", new XdmAtomicValue(expression));

            string sFinalOutput = transform("Eval", xDoc, new System.IO.StringReader(inXsl.OuterXml),
                sd);
            
            return sFinalOutput;

        }

        public string evaluate(string inputMessage)
        {
            return evaluate(inputMessage, "");
        }

        public string evaluate(string inputMessage, string ruleSet)
        {
            string sSchemaResults = validateSchema(inputMessage);
            string sRuleResult = evaluateRuleSet(inputMessage, ruleSet);

            return postProcessing(sRuleResult, sSchemaResults);
        }

        private string evaluateRuleSet(string inputMessage, string ruleSet)
        {
            DateTime startDateTime = DateTime.Now;
            
            //Get TranType and SubTrans of Message
            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml(inputMessage);
            XPathNavigator xInMessage = xDoc.CreateNavigator();
            XmlNamespaceManager xManagerResponse = new XmlNamespaceManager(xInMessage.NameTable);
        
            //Get the TransType and TransSubType codes from the Acord Message
            //this will be used to determine the scope of which rules are run
            XPathNavigator xTranType = xInMessage.SelectSingleNode("//TransType", xManagerResponse);
            if (xTranType != null)
            {
                sTranType = xTranType.Value;
                sTranTypeTC = xTranType.GetAttribute("tc", "");
            }
            XPathNavigator xSubTranType = xInMessage.SelectSingleNode("//TransSubType", xManagerResponse);
            if (xSubTranType != null)
            {
                sSubTranType = xSubTranType.Value;
                sSubTranTypeTC = xSubTranType.GetAttribute("tc", "");
            }

            //Read in RuleSet 
            XmlDocument inRuleSet = new XmlDocument();
            inRuleSet.LoadXml(ruleSet);
            XmlDocument inXsl = loadInternalXmlDocument("rules.xslt");

            //This holds any key-value pairs used to pass parms to the xsl
            Hashtable sd = new Hashtable();
            sd.Add("filter1", new XdmAtomicValue(sTranTypeTC));
            sd.Add("filter2", new XdmAtomicValue(sSubTranTypeTC));
                        
            //Output the new Transform to run against the Message
            string sOutputXml = transform("OutputXml", inRuleSet, new System.IO.StringReader(inXsl.OuterXml), sd);

            //Now run the rules against the message
            string sFinalOutput=  transform("Ruleset", xDoc, new System.IO.StringReader(sOutputXml), null);

            //Get the elapsed time for the rule run
            DateTime endDateTime = DateTime.Now;
            long elapsedTicks = endDateTime.Ticks - startDateTime.Ticks;
            elapsedRulesetSpan = new TimeSpan(elapsedTicks);
       
            return sFinalOutput;
        }

        private string postProcessing(string sRuleResults,string schemaResults)
        {
            Hashtable sd = new Hashtable();
            //Add in post rule values and nodes via transform
            
            sd.Add("rulesetruntime", new XdmAtomicValue(string.Format("{0}m.{1}s.{2}ms",
                elapsedRulesetSpan.Minutes, elapsedRulesetSpan.Seconds, elapsedRulesetSpan.Milliseconds)));
            sd.Add("schemaruntime", new XdmAtomicValue(string.Format("{0}m.{1}s.{2}ms",
                elapsedSchemaSpan.Minutes, elapsedSchemaSpan.Seconds, elapsedSchemaSpan.Milliseconds)));
            sd.Add("transtype", new XdmAtomicValue(sTranType));
            sd.Add("transtypetc", new XdmAtomicValue(sTranTypeTC));
            sd.Add("transsubtype", new XdmAtomicValue(sSubTranType));
            sd.Add("transsubtypetc", new XdmAtomicValue(sSubTranTypeTC));
            sd.Add("transexedate", new XdmAtomicValue(DateTime.Now.ToString("yyyy-MM-dd")));
            sd.Add("transexetime", new XdmAtomicValue(DateTime.Now.ToString("HH:mm:sszzz")));
            sd.Add("version", new XdmAtomicValue(AssemblyVersion));
            if (schemaResults.Length > 0)
            {
               
                DocumentBuilder builder = processor.NewDocumentBuilder();
                XdmNode x = builder.Build(XmlReader.Create(new StringReader(schemaResults)));
                sd.Add("schemaresponse",x );
            }

            XmlDocument xOut = new XmlDocument();
            xOut.LoadXml(sRuleResults);
            XmlDocument inXsl2 = loadInternalXmlDocument("postRules.xslt");

            return  transform("PostProcess Xml", xOut, new System.IO.StringReader(inXsl2.OuterXml), sd);
        }
        
        private XmlDocument loadInternalXmlDocument(string resourceName)
        {
            XmlDocument inXsl = new XmlDocument();
            string xmlResouce = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
               + "." + resourceName;
            using (Stream s = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(xmlResouce))
            {
                inXsl.Load(s);
            }

            return inXsl;
        }

        private string transform(string sDocType, XmlDocument inXml, System.IO.TextReader inXslt, Hashtable sd)
        {
            StringBuilder sb = new StringBuilder();
            XsltCompiler compiler = null;
            try
            {
                XdmNode input = processor.NewDocumentBuilder().Build(inXml);
                compiler = processor.NewXsltCompiler();
                compiler.ErrorList = new ArrayList();
                XsltTransformer transformer  = compiler.Compile(inXslt).Load();

                if (sd != null)
                {
                    foreach (object sKey in sd.Keys)
                    {
                        transformer.SetParameter(new QName("", "", (string)sKey), (XdmValue)sd[sKey]);
                    }
                }
                transformer.InitialContextNode = input;
                Serializer serializer = new Serializer();                
                System.IO.StringWriter sw = new System.IO.StringWriter(sb);
                serializer.SetOutputWriter(sw);
                //Transform RuleSet into Working RuleSet
                transformer.Run(serializer);
            }
            catch (javax.xml.transform.TransformerConfigurationException ex)
            {
                StringBuilder sbException = new StringBuilder();                
                sbException.AppendFormat("Exception in {0}\n", sDocType);
                sbException.AppendLine(ex.getMessage());
                if (compiler.ErrorList != null)
                {
                    foreach (StaticError s in compiler.ErrorList)
                        sbException.AppendLine(s.Message);
                }

                throw new Exception(sbException.ToString(), ex);
                
            }
            return sb.ToString();
        }

        public string AssemblyVersion
        {
            get
            {
                return Assembly.GetExecutingAssembly().GetName().Version.ToString();
            }
        }
        #endregion
    }
}
